﻿using System;
using System.Collections.Generic;
using AcademicPlanner.Domain.Models;
using AcademicPlanner.Data;
using AcademicPlanner.Data.Repositories;
using AcademicPlanner.Data.Entities;

namespace AcademicPlanner.Domain
{
	/// <summary>
	/// ModelsFountain is the singleton class that serves as a factory of
	/// <c>Model</c>s. It also internally caches the <c>Model</c>s it fabricates.
	/// </summary>
	/// <version>1.0</version>
	/// <since>1.0</since>
    class ModelsFountain
    {
		private Dictionary<string, Model> models = new Dictionary<string, Model>();


		/// <summary>
		/// Gets the <c>DomainMaster</c> of the AcademicPlanner application.
		/// </summary>
		/// <version>1.0</version>
		/// <since>1.0</since>
		protected DomainMaster DomainMaster { get { return DomainMaster.GetInstance(); } }


		private ModelsFountain() 
		{
		}

		private class InstanceHolder 
		{
			internal static ModelsFountain INSTANCE = new ModelsFountain(); 
		}


		/// <summary>
		/// Gets the single ModelsFountain instance. 
		/// </summary>
		/// <returns>The single ModelsFountain instance.</returns>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static ModelsFountain GetInstance()
		{ 
			return InstanceHolder.INSTANCE; 
		}


		/// <summary>
		/// Gets or creates the <c>Model</c> represented by the given <c>Entity</c>.
		/// This method tries to read from its cache first before creating the <c>Model</c>
		/// by reflection.
		/// </summary>
		/// <typeparam name="TModel">The type of the <c>Model</c> to get/create.</typeparam>
		/// <param name="pEntity">The <c>Entity</c> representing the <c>Model</c> to get/create.</param>
		/// <returns>The <c>Model</c> represented by the given <c>Entity</c>.</returns>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public TModel GetModel<TModel>(Entity pEntity) where TModel : Model
		{
			TModel rMod = null;
			Model vWaste;
			string vKey = string.Format("{0}-{1}", typeof(TModel).Name, pEntity.Id);

			models.TryGetValue(vKey, out vWaste);
			if (vWaste != null) rMod = (TModel)vWaste;
			else
			{
				rMod = Activator.CreateInstance<TModel>();
				rMod.Init(pEntity);
				models.Add(vKey, rMod);
			}

			return rMod;
		}

		/// <summary>
		/// Gets or creates a <c>Person</c>. This method is a convenience call
		/// to <see cref="M:GetModel<TModel>"/>.
		/// </summary>
		/// <param name="pEntity">The <c>PersonEntity</c> representing the <c>Person</c> to get/create.</param>
		/// <returns>The <c>Person</c> represented by the given <c>PersonEntity</c>.</returns>
		/// <seealso cref="M:GetModel<TModel>"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public Person GetPerson(PersonEntity pEntity)
		{
			return GetModel<Person>(pEntity);
		}

		/// <summary>
		/// Gets or creates a <c>User</c>. This method is a convenience call
		/// to <see cref="M:GetModel<TModel>"/>.
		/// </summary>
		/// <param name="pEntity">The <c>UserEntity</c> representing the <c>User</c> to get/create.</param>
		/// <returns>The <c>User</c> represented by the given <c>UserEntity</c>.</returns>
		/// <seealso cref="M:GetModel<TModel>"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public User GetUser(UserEntity pEntity)
		{
			return GetModel<User>(pEntity);
		}

		/// <summary>
		/// Gets or creates a <c>Account</c>. This method is a convenience call
		/// to <see cref="M:GetModel<TModel>"/>.
		/// </summary>
		/// <param name="pEntity">The <c>AccountEntity</c> representing the <c>Account</c> to get/create.</param>
		/// <returns>The <c>Account</c> represented by the given <c>AccountEntity</c>.</returns>
		/// <seealso cref="M:GetModel<TModel>"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public Account GetAccount(AccountEntity pEntity)
		{
			return GetModel<Account>(pEntity);
		}

		/// <summary>
		/// Creates a new <c>Person</c>. This method requests a new <c>PersonEntity</c>
		/// from the data layer and creates a <c>Person</c> based on it.
		/// </summary>
		/// <returns>A new <c>Person</c>.</returns>
		/// <seealso cref="P:DomainMaster.DSAS"/>
		/// <seealso cref="P:AcademicPlanner.Data.DataSourceAccessSupervisor.PersonEntityRepo"/>
		/// <seealso cref="M:AcademicPlanner.Data.Repositories.PersonEntityRepository.GetNew"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public Person GetNewPerson()
		{
			Person rPers = null;
			var vEnt = DomainMaster.DSAS.PersonEntityRepo.GetNew();

			rPers = new Person(vEnt);
			
			return rPers;
		}

		/// <summary>
		/// Creates a new <c>Account</c>. This method requests a new <c>AccountEntity</c>
		/// from the data layer and creates a <c>Account</c> based on it.
		/// </summary>
		/// <returns>A new <c>Account</c>.</returns>
		/// <seealso cref="P:DomainMaster.DSAS"/>
		/// <seealso cref="P:AcademicPlanner.Data.DataSourceAccessSupervisor.AccountEntityRepo"/>
		/// <seealso cref="M:AcademicPlanner.Data.Repositories.AccountEntityRepository.GetNew"/>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public Account GetNewAccount()
		{
			Account rMod = null;
			var vEnt = DomainMaster.DSAS.AccountEntityRepo.GetNew();

			rMod = new Account(vEnt);

			return rMod;
		}
    }
}
